(ns util.pdh
  (:use util.os
        [util.utils :only [exception-nil]]
        clojure.tools.logging)
  (:import [org.hyperic.sigar.win32 Pdh]))

(when-not (windows?) (throw (Exception. "pdh could only work on Windows.")))

(defn pdh-command [& args]
  (Pdh/main (into-array String args)))

(defn pdh-command-line [line]
  (apply pdh-command (.split line "\\s+")))

;;;

(defn new-pdh []
  (Pdh.))

(def ^:dynamic *pdh* (new-pdh))

(defn pdh []
  (or *pdh* (new-pdh)))

;;;

(defn pdh-fmt-raw [value]
  value)

(defn pdh-fmt-long [value]
  (long value))


(def ^:dynamic *pdh-value-format* pdh-fmt-long)

(defmacro pdh-as-raw [& body]
  `(binding [*pdh-value-format* pdh-fmt-raw]
    ~@body))

(defmacro pdh-as-long [& body]
  `(binding [*pdh-value-format* pdh-fmt-long]
    ~@body))

;;;

(defn pdh-query
  ([query]
    (try
      (*pdh-value-format* (.getFormattedValue (pdh) query))
      (catch Exception e
        (error e (str "PDH counter failed: " query))
        (throw e))))
  ([object key]
    (pdh-query (format "\\%s\\%s" object key)))
  ([object instance key]
    (pdh-query (format "\\%s(%s)\\%s" object instance key))))

(defn pdh-objects []
  (seq (Pdh/getObjects)))

(defn pdh-instances [object]
  (seq (Pdh/getInstances object)))

(defn pdh-keys [object]
  (seq (Pdh/getKeys object)))

;;; common functions

(defn pdh-process [pid key]
  (pdh-query "Process" pid key))

(defn pdh-process-id [pid]
  (pdh-process pid "ID Process"))

(defn- annotate-processes [processes]
  (loop [left processes
         result []
         counter {}]
    (if (empty? left)
      result
      (let [p (first left)
            c (counter p)
            px (if c (str p "#" c) p)]
        (recur (rest left) (conj result px) (assoc counter p (if c (inc c) 1)))))))

(defn ^:dynamic pdh-process-instance [pid]
  (first (filter #(= pid (exception-nil (pdh-process-id %))) (annotate-processes (pdh-instances "Process")))))

(defn pdh-working-set [process]
  (pdh-process process "Working Set"))

(defn pdh-private-bytes [process] ; virtual memory in task manager
  (pdh-process process "Private Bytes"))

(defn pdh-working-set-private [process] ; memory in task manager
  (pdh-process process "Working Set - Private"))

(try
  (.getRawValue (pdh) "\\Process(_Total)\\Working Set - Private")
  (catch Exception e
    (try
      (.getRawValue (pdh) "\\Process(_Total)\\Working Set")
      (def pdh-working-set-private pdh-working-set)
      (catch Exception e))))

(defn pdh-process-cpu [process] ; XXX: TODO: FIXME: not working:(
  (pdh-process process "% Processor Time"))

(defn pdh-total-cpu []
  (pdh-query "Processor" "_Total" "% Processor Time"))

(defmacro pdh-with-pid-cache [& body]
  `(binding [pdh-process-instance (memoize pdh-process-instance)]
    ~@body))



